package com.itheima.algorithm.blockingqueue;

import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author: TylerZhong
 * @description:
 */
public class BlockingQueue2<E> implements BlockingQueue<E> {
    private E[] array;
    private AtomicInteger size = new AtomicInteger();
    private int tail;
    private int head;
    private ReentrantLock tailLock = new ReentrantLock();
    Condition tailWaits = tailLock.newCondition();
    private ReentrantLock headLock = new ReentrantLock();
    Condition headWaits = headLock.newCondition();

    public BlockingQueue2(int capacity) {
        array = (E[]) new Object[capacity];
    }

    @Override
    public void offer(E e) throws InterruptedException {
        int c;
        tailLock.lockInterruptibly();
        try {
            while (isFull()) {
                tailWaits.await();
            }
            array[tail] = e;
            if (++tail == array.length) {
                tail = 0;
            }
            c = size.getAndIncrement();
            if (c+1 < array.length) {
                tailWaits.signal();
            }
        }finally {
            tailLock.unlock();
        }

        if (c == 0) {
            headLock.lock();
            try {
                headWaits.signal();
            } finally {
                headLock.unlock();
            }
        }
    }

    @Override
    public boolean offer(E e, long timeout) throws InterruptedException { // 毫秒
        boolean flag;
        tailLock.lockInterruptibly();
        try {
            long t = TimeUnit.MILLISECONDS.toNanos(timeout);
            while (isFull()) {
                if (t <= 0) {
                    return false;
                }
                t = tailWaits.awaitNanos(t); // 最多等待多少纳秒， 1s 4s 返回值代表剩余时间
            }
            array[tail] = e;
            if (++tail == array.length) {
                tail = 0;
            }
            size.getAndIncrement();

            flag = true;
        } finally {
            tailLock.unlock();
        }

        headLock.lock();
        try {
            headWaits.signal();
        } finally {
            headLock.unlock();
        }
        return flag;
    }

    @Override
    public E poll() throws InterruptedException {
        int c;
        E e;
        headLock.lockInterruptibly();
        try {
            while (isEmpty()) {
                headWaits.await();
            }
            e = array[head];
            array[head] = null; // help GC
            if (++head == array.length) {
                head = 0;
            }
            c = size.getAndDecrement();
            if (c > 1) {
                headWaits.signal();
            }
        }finally {
            headLock.unlock();
        }

        if (c == array.length) {
            tailLock.lock();
            try {
                tailWaits.signal();
            } finally {
                tailLock.unlock();
            }
        }

        return e;
    }

    public boolean isEmpty() {
        return size.get() == 0;
    }

    public boolean isFull() {
        return size.get() == array.length;
    }

    @Override
    public String toString() {
        return Arrays.toString(array);
    }

    public static void main(String[] args) throws InterruptedException {
        BlockingQueue2<String> queue = new BlockingQueue2<>(3);
        queue.offer("元素1");
        queue.offer("元素2");

        new Thread(() -> {
            try {
                queue.offer("元素3");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "offer").start();

        new Thread(() -> {
            try {
                queue.poll();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "poll").start();
    }

    public void test1() throws InterruptedException {
        BlockingQueue2<Object> queue = new BlockingQueue2<>(3);
        queue.offer("任务1");

        new Thread(() -> {
            try {
                queue.offer("任务2");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "offer").start();

        new Thread(() -> {
            try {
                System.out.println(queue.poll());
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "poll").start();
    }

    public void test2() throws InterruptedException {
        BlockingQueue2<String> queue = new BlockingQueue2<>(3);

        new Thread(() -> {
            try {
                String poll = queue.poll();
                System.out.println(poll);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "poll_1").start();

        new Thread(() -> {
            try {
                String poll = queue.poll();
                System.out.println(poll);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "poll_2").start();

        new Thread(() -> {
            try {
                queue.offer("元素");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "offer").start();
    }
}
